package com.ms.orm.meta;

import com.ms.common.util.ReflectUtils;
import com.ms.common.util.StringUtils;
import com.ms.orm.constants.Constants;
import com.ms.orm.constants.IdType;
import com.ms.orm.exception.BaseSqlException;
import com.ms.orm.util.MetaDataOperator;
import com.ms.orm.util.sql.SqlScriptUtils;
import lombok.*;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.function.Predicate;

import static java.util.stream.Collectors.joining;

/**
 * @author zdh
 * 对于与数据库字段映射
 */
@Data
@ToString
public class EntityAndTableInfo implements Serializable {

    private Class entityClass;

    private String  simpleClassName;
    /**
     * 数据库表名
     */
    private String tableName;
    /**
     * 主键信息
     */
    private PrimaryKey primaryKey;

    /**
     * 属性字段映射
     */
    private List<PropertyAndColumnInfo> properties;

    /**
     * 属性字段映射 map
     */
    private Map<String,PropertyAndColumnInfo> columnMap;


////////////////////////////////////////////////////////////////

    /**
     * 是否开启下划线转驼峰
     * <p>
     * 未注解指定字段名的情况下,用于自动从 property 推算 column 的命名
     */
    private boolean underCamel;

    /**
     * 缓存包含主键及字段的 sql select
     */
    @Setter(AccessLevel.NONE)
    @Getter(AccessLevel.NONE)
    private String allSqlSelect;

    /**
     * 缓存主键字段的 sql select
     */
    @Setter(AccessLevel.NONE)
    @Getter(AccessLevel.NONE)
    private String sqlSelect;

    /**
     * 表字段是否启用了插入填充
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private boolean withInsertFill;

    /**
     * 表字段是否启用了更新填充
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private boolean withUpdateFill;

    /**
     * 表字段是否启用了逻辑删除
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private boolean withLogicDelete;
    /**
     * 逻辑删除字段
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private PropertyAndColumnInfo logicDeleteFieldInfo;


    /**
     * 表字段是否启用了乐观锁
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private boolean withVersion;


    /**
     * 乐观锁字段
     */
    @Getter
    @Setter(AccessLevel.NONE)
    private PropertyAndColumnInfo versionFieldInfo;

    /**
     * 排序列表
     */
    @Setter
    private List<PropertyAndColumnInfo> orderByFields;


    /**
     * 是否有主键
     * @return 是否有
     */
    public boolean havePK() {
        return StringUtils.isNotNull(primaryKey);
    }

    /**
     * 获取主键的 select sql 片段
     * @return sql 片段
     */
    public String getKeySqlSelect() {
        if (sqlSelect != null) {
            return sqlSelect;
        }
        if (havePK()) {
            sqlSelect = primaryKey.getColumnName();
            /*if (resultMap == null && keyRelated) {
                sqlSelect += (AS + keyProperty);
            }*/
        } else {
            sqlSelect = Constants.EMPTY;
        }
        return sqlSelect;
    }

    /**
     * 获取包含主键及字段的 select sql 片段
     *
     * @return sql 片段
     */
    public String getAllSqlSelect() {
        if (allSqlSelect != null) {
            return allSqlSelect;
        }
        allSqlSelect = chooseSelect(PropertyAndColumnInfo::isFieldExit);
        return allSqlSelect;
    }

    /**
     * 获取需要进行查询的 select sql 片段：比如  id, name,age 等
     * @param predicate 过滤条件
     * @return sql 片段
     */
    public String chooseSelect(Predicate<PropertyAndColumnInfo> predicate) {
        return properties.stream().filter(predicate).map(PropertyAndColumnInfo::getColumnName).collect(joining(Constants.COMMA));
        /*String sqlSelect = getKeySqlSelect();
        //1、选择指定 字段集合
        String fieldsSqlSelect = properties.stream().filter(predicate).map(PropertyAndColumnInfo::getColumnName).collect(joining(Constants.COMMA));
        if (StringUtils.isNotBlank(sqlSelect) && StringUtils.isNotBlank(fieldsSqlSelect)) {
            //2、主键字段不为空、其他字段也不为空
            return sqlSelect + Constants.COMMA + fieldsSqlSelect;
        } else if (StringUtils.isNotBlank(fieldsSqlSelect)) {
            //3、主键字段为空、其他字段也不为空
            return fieldsSqlSelect;
        }
        return sqlSelect;*/
    }

    /**
     * 获取 insert 时候主键 sql 脚本片段
     * <p>insert into table (字段) values (值)</p>
     * <p>位于 "值" 部位</p>
     *
     * @param batch 是否批量插入
     * @param prefix
     * @param newLine
     * @return  sql 脚本片段
     */
    public String getKeyInsertSqlProperty(final boolean batch, final String prefix, final boolean newLine) {
        final String newPrefix = prefix == null ? Constants.EMPTY : prefix;
        if (havePK()) {
            final String prefixKeyProperty = newPrefix + primaryKey.getPropertyName();
            String keyColumn = SqlScriptUtils.safeParam(prefixKeyProperty) + Constants.COMMA;
            if (primaryKey.getIdType() == IdType.AUTO) {
                if (batch) {
                    // 批量插入必须返回空自增情况下
                    return Constants.EMPTY;
                }
                return SqlScriptUtils.convertIf(keyColumn, String.format("%s != null", prefixKeyProperty), newLine);
            }
            return keyColumn + (newLine ? Constants.NEWLINE : Constants.EMPTY);
        }
        return Constants.EMPTY;
    }

    /**
     * 获取 insert 时候主键 sql 脚本片段
     * <p>insert into table (字段) values (值)</p>
     * <p>位于 "字段" 部位</p>
     *
     * @return sql 脚本片段
     */
    public String getKeyInsertSqlColumn(final boolean batch, final boolean newLine) {
        if (havePK()) {
            if (primaryKey.getIdType() == IdType.AUTO) {
                if (batch) {
                    // 批量插入必须返回空自增情况下
                    return Constants.EMPTY;
                }
                return SqlScriptUtils.convertIf(primaryKey.getColumnName() + Constants.COMMA, String.format("%s != null", primaryKey.getPropertyName()), newLine);
            }
            return primaryKey.getColumnName() + Constants.COMMA + (newLine ? Constants.NEWLINE : Constants.EMPTY);
        }
        return Constants.EMPTY;
    }

    /**
     * 获取所有 insert 时候插入值 sql 脚本片段
     * <p>insert into table (字段) values (值)</p>
     * <p>位于 "值" 部位</p>
     *
     * <li> 自动选部位,根据规则会生成 if 标签 </li>
     *
     * @return sql 脚本片段
     */
    public String getAllInsertSqlPropertyMaybeIf(final String prefix) {
        final String newPrefix = prefix == null ? Constants.EMPTY : prefix;
        return getKeyInsertSqlProperty(false, newPrefix, true) + properties.stream()
                .map(i -> i.getInsertSqlPropertyMaybeIf(newPrefix)).filter(Objects::nonNull).collect(joining(Constants.NEWLINE));
    }

    /**
     * 获取 insert 时候字段 sql 脚本片段
     * <p>insert into table (字段) values (值)</p>
     * <p>位于 "字段" 部位</p>
     *
     * <li> 自动选部位,根据规则会生成 if 标签 </li>
     *
     * @return sql 脚本片段
     */
    public String getAllInsertSqlColumnMaybeIf(final String prefix) {
        final String newPrefix = prefix == null ? Constants.EMPTY : prefix;
        return getKeyInsertSqlColumn(false, true) + properties.stream().map(i -> i.getInsertSqlColumnMaybeIf(newPrefix))
                .filter(Objects::nonNull).collect(joining(Constants.NEWLINE));
    }

    /**
     * 获取所有的查询的 sql 片段
     *
     * @param ignoreLogicDelFiled 是否过滤掉逻辑删除字段
     * @param withId              是否包含 id 项
     * @param prefix              前缀
     * @return sql 脚本片段
     */
    public String getAllSqlWhere(boolean ignoreLogicDelFiled, boolean withId, final String prefix) {
        final String newPrefix = prefix == null ? Constants.EMPTY : prefix;
        String filedSqlScript = properties.stream()
                .filter(i -> {
                    if (ignoreLogicDelFiled) {
                        return !(isWithLogicDelete() && i.isLogicDelete());
                    }
                    return true;
                })
                .map(i -> i.getSqlWhere(newPrefix)).filter(Objects::nonNull).collect(joining(Constants.NEWLINE));
        if (!withId || primaryKey==null || StringUtils.isBlank(primaryKey.getColumnName())) {
            return filedSqlScript;
        }
        String newKeyProperty = newPrefix + primaryKey.getColumnName();
        String keySqlScript = primaryKey.getColumnName() + Constants.EQUALS + SqlScriptUtils.safeParam(newKeyProperty);
        return SqlScriptUtils.convertIf(keySqlScript, String.format("%s != null", newKeyProperty), false)
                + Constants.NEWLINE + filedSqlScript;
    }

    /**
     * 获取所有的 sql set 片段
     *
     * @param ignoreLogicDelFiled 是否过滤掉逻辑删除字段
     * @param prefix              前缀
     * @return sql 脚本片段
     */
    public String getAllSqlSet(boolean ignoreLogicDelFiled, final String prefix) {
        final String newPrefix = prefix == null ? Constants.EMPTY : prefix;
        return properties.stream()
                .filter(i -> {
                    if (ignoreLogicDelFiled) {
                        return !(isWithLogicDelete() && i.isLogicDelete());
                    }
                    return true;
                }).map(i -> i.getSqlSet(newPrefix)).filter(Objects::nonNull).collect(joining(Constants.NEWLINE));
    }

    /**
     * 获取逻辑删除字段的 sql 脚本
     *
     * @param startWithAnd 是否以 and 开头
     * @param isWhere      是否需要的是逻辑删除值
     * @return sql 脚本
     */
    public String getLogicDeleteSql(boolean startWithAnd, boolean isWhere) {
        if (withLogicDelete) {
            String logicDeleteSql = formatLogicDeleteSql(isWhere);
            if (startWithAnd) {
                logicDeleteSql = " AND " + logicDeleteSql;
            }
            return logicDeleteSql;
        }
        return Constants.EMPTY;
    }

    /**
     * format logic delete SQL, can be overrided by subclass
     * github #1386
     *
     * @param isWhere true: logicDeleteValue, false: logicNotDeleteValue
     * @return sql
     */
    protected String formatLogicDeleteSql(boolean isWhere) {
        final String value = isWhere ? logicDeleteFieldInfo.getLogicNotDeleteValue() : logicDeleteFieldInfo.getLogicDeleteValue();
        if (isWhere) {
            if (Constants.NULL.equalsIgnoreCase(value)) {
                return logicDeleteFieldInfo.getColumnName() + " IS NULL";
            } else {
                return logicDeleteFieldInfo.getColumnName() + Constants.EQUALS + String.format(logicDeleteFieldInfo.isCharSequence() ? "'%s'" : "%s", value);
            }
        }
        final String targetStr = logicDeleteFieldInfo.getColumnName() + Constants.EQUALS;
        if (Constants.NULL.equalsIgnoreCase(value)) {
            return targetStr + Constants.NULL;
        } else {
            return targetStr + String.format(logicDeleteFieldInfo.isCharSequence() ? "'%s'" : "%s", value);
        }
    }


    public List<PropertyAndColumnInfo> getFieldList() {
        return Collections.unmodifiableList(properties);
    }

    public List<PropertyAndColumnInfo> getOrderByFields() {
        if (null == this.orderByFields) {
            this.orderByFields = new LinkedList<>();
        }
        return this.orderByFields;
    }

    @Deprecated
    public boolean isLogicDelete() {
        return withLogicDelete;
    }

    /**
     * 获取对象属性值
     *
     * @param entity   对象
     * @param property 属性名
     * @return 属性值
     * @since 3.4.4
     */
    public Object getPropertyValue(Object entity, String property) {
        try {
            return MetaDataOperator.getProperValue(entity, property);
        } catch (Exception e) {
            throw new BaseSqlException("Error: Cannot read property in %s.  Cause:"+entity.getClass().getSimpleName(), e);
        }
    }

    /**
     * 设置对象属性值
     *
     * @param entity   实体对象
     * @param property 属性名
     * @param values   参数
     */
    public void setPropertyValue(Object entity, String property, Object... values) {
        try {
            MetaDataOperator.setProperValue(entity,property, values);
        } catch (Exception e) {
            throw new BaseSqlException("Error: Cannot write property in %s.  Cause:"+ entity.getClass().getSimpleName(), e);
        }
    }

    /**
     * 创建实例
     * @param <T> 泛型
     * @return 初始化实例
     * @since 3.5.0
     */
    @SuppressWarnings("unchecked")
    public <T> T newInstance() {
        Constructor<?> defaultConstructor = ReflectUtils.getConstructor(entityClass,0);
        if (!defaultConstructor.isAccessible()) {
            defaultConstructor.setAccessible(true);
        }
        try {
            return (T) defaultConstructor.newInstance();
        } catch (ReflectiveOperationException e) {
            throw new BaseSqlException(e.getMessage());
        }
    }



}
